
#include <math.h>
#include <sstream>
#include "camera.h"
#include "color.h"
#include "random.h"
namespace gemren
{
	coord camera::local_dir = coord(0,0,1);
	coord camera::local_right = coord(1,0,0);
	coord camera::local_up = coord(0,1,0);

	void camera::announce_properties(const std::string& prefix, property_announcer announce)
	{
		announce((prefix+ ".pos").c_str(), ANNOUNCED_STRING, val_to_str(viewpoint).c_str());
		announce((prefix+ ".dir").c_str(), ANNOUNCED_STRING, val_to_str(look*local_dir).c_str());
		announce((prefix+ ".fov").c_str(), ANNOUNCED_FLOAT, val_to_str(2*FOV*180.0f/3.141592654f).c_str());
		announce((prefix+ ".aperture").c_str(), ANNOUNCED_FLOAT, val_to_str(aperture).c_str());
		announce((prefix+ ".focus").c_str(),     ANNOUNCED_FLOAT, val_to_str(focus).c_str());
		announce((prefix+ ".movespeed").c_str(), ANNOUNCED_FLOAT, val_to_str(move_speed).c_str());
		announce((prefix+ ".pitchspeed").c_str(), ANNOUNCED_FLOAT, val_to_str(pitch_speed).c_str());
		announce((prefix+ ".roulette_min").c_str(), ANNOUNCED_FLOAT, val_to_str(rr_min).c_str());
		announce((prefix+ ".roulette_max").c_str(), ANNOUNCED_FLOAT, val_to_str(rr_max).c_str());
	}

	err_t camera::set_my_string(std::string& name, const std::string& value)
	{
		if(name == "pos")
		{
			std::stringstream ss(value);
			coord buf;
			ss>>buf.x>>buf.y>>buf.z;
			if(ss.fail()) return ERR_BAD_VAL;
			set_pos(buf);
		}
		else if(name == "dir")
		{
			std::stringstream ss(value);
			coord buf;
			ss>>buf.x>>buf.y>>buf.z;
			if(ss.fail()) return ERR_BAD_VAL;
			set_dir(buf);
		}
		else return ERR_BAD_PROP;
		return ERR_OK;
	}
	err_t camera::set_my_float(std::string& name, const float value)
	{
		if(name == "fov")				
			FOV = 0.5f*embree::clamp(value, 5.0f, 175.0f)*PI/180.0f;
		else if(name == "aperture")		
			aperture = embree::clamp(value, 0.0f, 1000.0f);
		else if(name == "focus")
			focus = value;
		else if(name == "roulette_min")
		{
			rr_min = embree::clamp(value, 0.0f, 0.99f);
			rr_max = std::max(rr_max, rr_min);
		}
		else if(name == "roulette_max")
		{
			rr_max = embree::clamp(value, 0.0f, 0.99f);
			rr_min = std::min(rr_max, rr_min);
		}
		else if(name == "movespeed")
		{ 
			move_speed = embree::clamp(value, 0.0f, 100000.0f);	
			return ERR_OK_NOSCRATCH;
		}
		else if(name == "pitchspeed")
		{
			pitch_speed = embree::clamp(value, 0.0f, 1000.0f);
			return ERR_OK_NOSCRATCH;
		}
		
		else return ERR_BAD_PROP;
		return ERR_OK;
	}
	

	camera::camera(scene* myscene):
		myscene(myscene), 
		look(matrix4x4::identity()), 
		FOV(3.141592654f*0.25f), 
		randoms(0), 
		maxheight(0), 
		move_speed(10), 
		pitch_speed(1),
		rr_min(0.1f),
		rr_max(0.9f),
		aperture(0),
		focus(0)
	{}
	std::string dummy("error");

	//when user clicks somewhere in the image, find the visible material
	const std::string& camera::get_onscreen_material_name(int x, int y, int width, int height) const 
	{
		coord cur, dx, dy;
		rasterizer_data(cur, dx,dy, width, height);
		cur += float(x)*dx + float(y)*dy;
		intersection i;
		myscene->first_intersection(i, viewpoint, cur);
		if(i)
			return myscene->material_name(i.triptr->insidemat);
		else
			return dummy;
		
	}

	//computation of a row of the image done in a single thread
	void camera::scan_line(::size_t tid, void* ptr, ::size_t elt)
	{
		camera_data* data = (camera_data*) ptr;
		
		float rspp = embree::rcp(float(data->spp+1));
		float spp = float(data->spp);
		bool isfast = data->fast;
		random ran = data->randoms[elt];
		embree::Col3f* pos = data->img + (elt)*(data->width);
		coord dir = data->leftup + float(elt)*data->dy;
		coord& step = data->dx;
		for(int i = 0; i < data->width; ++i)
		{
			ray r(data->myscene, &ran);
			coord aadir = isfast ? dir : dir + ran.getf()*data->dx + ran.getf()*data->dy; //antialasing
			embree::Col3f c( r.shoot(data->viewpoint, aadir, isfast, data->aperture, data->focus, data->rr_min, data->rr_max));
			*pos = rspp * (spp* *pos + c) ;
			++pos;
			dir += step;
		}
		data->randoms[elt] = ran;
	}
	void camera::set_view(const coord& pos, const coord& dir)
	{
		set_pos(pos);
		set_dir(dir);
	}
	
	void camera::set_pos(const coord& pos)
	{
		viewpoint = pos;
	}
	void camera::set_dir(const coord& dir)
	{
		coord lookdir = embree::normalize(dir);
		coord up;
		coord right;
		if(abs(dir.x)<EPSILON && abs(dir.z)<EPSILON)
			up = coord(0,0,-1); 
		else 
		{
			up = coord(0,1,0); 
			up = embree::normalize(up - embree::dot(up, lookdir) * lookdir);
		}
		right = embree::cross(up,lookdir);
		look = matrix4x4::linear_transformation(right, up, lookdir);
	}
	void camera::set_FOV(float_type fov)
	{
		float_type pi = 3.141592654f;
		FOV =  pi  * fov /360.0f;
		if(FOV < - 0.5*pi) FOV = -0.5f*pi;
		else if (FOV > 0.5*pi) FOV = 0.5f*pi;
	}
	
	void camera::set_DOF(float aperture, float focus)
	{
		this->aperture = aperture;
		this->focus = focus;
	}
	void camera::dir_delta(float dx, float dy, float* newdir)
	{
		float pitch = embree::asin( (look*local_dir).y);
		float newpitch = embree::clamp(pitch+pitch_speed*dy, -PI*0.49f, PI*0.49f );
		look =look* matrix4x4::rotateX(pitch) * matrix4x4::rotateY(pitch_speed*dx) * matrix4x4::rotateX(-newpitch);//*look;
		if(newdir)
		{
			coord nd = look * local_dir;
			newdir[0] = nd.x;
			newdir[1] = nd.y;
			newdir[2] = nd.z;
		}
	}
	void camera::pos_delta(float dx, float dy, float dz, float* newpos)
	{
		viewpoint += move_speed*dx*(look*local_right) + move_speed*dy*(look*local_up) + move_speed*dz*(look*local_dir);
		if(newpos)
		{
			newpos[0] = viewpoint.x;
			newpos[1] = viewpoint.y;
			newpos[2] = viewpoint.z;
		}
	}
	
	void camera::rasterizer_data(coord& leftup, coord& dx, coord& dy, const int width, const int height) const
	{
		coord lookdir = look * local_dir;
		coord right = look * local_right;
		coord up = look * local_up;
		float_type ar = float_type(height)/width;
		float_type fovtan = tan(FOV);
		leftup = lookdir - (fovtan * right)  + (fovtan*ar *up);
		right = 2* fovtan * right;
		coord down = -2* fovtan*ar * up;

		dx = (1.00f/width) * right;
		dy = (1.00f/height) * down;
	}
	void camera::snap(embree::Col3f* img, int width, int height, int spp, bool fast)
	{
		snap(img, width, height, 0, 0, width, height, spp, fast);
	}
	void camera::snap(embree::Col3f* img, int width, int height, int l, int t, int r, int b, int spp, bool fast)
	{//TODO up direction ?
		if(maxheight<height) //todo changed height
		{
			if(randoms) embree::alignedFree(randoms);
			randoms = (random*) embree::alignedMalloc(height*sizeof(random));
			for(int i = 0; i < height; ++i) randoms[i] = random(i*314);
			maxheight = height;
		}
		camera_data data;
		data.width = width;
		data.height = height;
		data.img = img;
		data.viewpoint = viewpoint;
		data.myscene = myscene;
		data.randoms = randoms;
		data.rr_min = rr_min;
		data.rr_max = rr_max;
		
		rasterizer_data(data.leftup, data.dx, data.dy, width, height);
		data.spp = spp;
		data.fast = fast;
		data.aperture = aperture;

		if(focus<EPSILON )
		{
			if(aperture>EPSILON)
			{
				intersection i;
				coord lookdir = look * local_dir;
				myscene->first_intersection(i, viewpoint, lookdir);
				if(i) 
					data.focus = i.t;
				else 
				{
					data.aperture = 0; //no DOF
					data.focus = 1;
				}
			}
			else data.focus = 1;
		}
		else
		data.focus = focus;
		embree::scheduler->addTask(scan_line, (void*)&data, height);
		embree::scheduler->go();
	}
	
}